package com.apes.scm.voucher.service.builder;

import com.apes.framework.api.SpringManager;
import com.apes.scm.account.account.model.AccountingMethod;
import com.apes.scm.account.account.model.InventoryConsign;
import com.apes.scm.account.account.model.InventoryMode;
import com.apes.scm.account.account.service.InventoryConsignService;
import com.apes.scm.account.method.Batch;
import com.apes.scm.account.method.BatchMethod;
import com.apes.scm.masterdata.party.model.PartyRole;
import com.apes.scm.voucher.model.Voucher;
import com.apes.scm.voucher.model.VoucherItem;
import com.apes.scm.voucher.repository.VoucherRepository;
import com.apes.scm.voucher.service.BusinessType;
import com.apes.scm.voucher.service.VoucherService;
import com.apes.scm.voucher.service.VoucherType;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import javax.persistence.EntityManager;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.Math.abs;
import static java.util.stream.Collectors.groupingBy;

/**
 * 功能：供应商寄售凭证构造器
 *
 * @author xul
 * @create 2018-10-18 9:26
 */
@Component
public class SupplierConsignVoucherBuilder extends AbstractVoucherBuilder {

    public SupplierConsignVoucherBuilder() {
        this.voucherType = VoucherType.SUPPLIER_CONSUME;
        this.BusinessTypes = new String[]{BusinessType.SUPPLIER_CONSUME_ORDER_VOUCHER, BusinessType.SUPPLIER_CONSUME_RETURN_VOUCHER};
    }

    /**
     * 功能：创建凭证
     *
     * @param voucher
     */
    @Override
    public Voucher create(Voucher voucher) {
        if (!("Supplier".equals(voucher.getPartner().getRole().getId()))) {
            throw new RuntimeException("采购凭证请指定供应角色的合作伙伴[" + voucher.getPartner().getId() + "]!");
        }

        /**保存凭证**/
        voucher = save(voucher);

        /**内部供应商需要做内部客户寄售消耗**/
        customerConsignConsume(voucher);

        /**暂估款**/
        changCurrentProvisional(voucher, -1);

        /**创建供应商帐单**/
        invoiced(voucher);

        return voucher;
    }

    @Autowired
    private InventoryConsignService icService;

    /**
     * 客户寄售凭证
     */
    private void customerConsignConsume(Voucher voucher) {
        // 内部供应商并且属于公司间
        if (!voucher.getPartner().getParty().getCorporate().inner() || voucher.getPartner().getParty() == voucher.getCompany().getParty()) {
            return;
        }
        boolean isReturn = voucher.isReturned();
        PartyRole company = getCompany(voucher.getPartner()); //供应商->公司
        PartyRole customer = getCustomer(voucher.getCompany()); //公司->客户

        Set<VoucherItem> voucherItems = new HashSet<>();
        voucher.getVoucherItems().stream().forEach(item -> new BatchMethod<Batch>() {
            @Override
            public Iterable<Batch> wrap() {
                if (!isReturn) {
                    List<InventoryConsign> inventoryConsigns = icService.findInventoryConsignForOrder(company, customer, item.getProduct(), item.computeBasePrice());
                    return icService.wrapBatch(inventoryConsigns, item.getUom(), ic -> ic.getBalanceQty() - ic.getFrostBalanceQty());
                } else {
                    List<InventoryConsign> inventoryConsigns = icService.findInventoryConsignForReturn(company, customer, item.getProduct(), item.computeBasePrice());
                    return icService.wrapBatch(inventoryConsigns, item.getUom(), ic -> ic.getConsumeQty() - ic.getFrostConsumeQty());
                }
            }

            @Override
            public void succeed(Batch batch, double increment) {
                VoucherItem voucherItem = new VoucherItem();
                voucherItem.setProduct(item.getProduct());
                voucherItem.setUom(item.getUom());
                voucherItem.setPriceUnit(item.getPriceUnit());
                voucherItem.setProductQty(isReturn ? -1 * increment : increment);
                voucherItem.setOriginItemId(item.getOriginItemId());
                voucherItem.setSourceItemId(String.valueOf(item.getSourceItemId()));

                InventoryConsign stock = (InventoryConsign) batch.getEntity();
                voucherItem.setTax(stock.getTax());
                voucherItem.setInventoryConsign(stock);
                voucherItem.setInventoryBatch(stock.getInventoryBatch());
                double qty = item.getUom().computeQty(increment, stock.getUom(), true);
                if (!isReturn) {
                    stock.changeFrostBalanceQty(qty);
                } else {
                    stock.changeFrostConsumeQty(qty);
                }
                icService.save(stock);
                voucherItems.add(voucherItem);
            }

            @Override
            public void error(double surplus) {
                throw new RuntimeException("商品：["+item.getProduct().getId()+"]" + item.getProduct().getName() + " 寄售库存数量少 " + surplus + item.getUom().getName() + "！");
            }
        }.execute(abs(item.getProductQty())));

        voucherItems.stream().collect(groupingBy(vi -> vi.getInventoryConsign().getDept()))
                .forEach((dept, vis) -> {
                    Voucher consignVoucher = new Voucher();
                    consignVoucher.setCompany(company);
                    consignVoucher.setPartner(customer);
                    consignVoucher.setVoucherType(VoucherType.CUSTOMER_CONSUME);
                    consignVoucher.setInventoryMode(InventoryMode.IM_OWN);
                    consignVoucher.setAccountingMethod(AccountingMethod.AM_PURCHASE);
                    consignVoucher.setDept(dept);
                    consignVoucher.setSource(voucher.getId());
                    consignVoucher.setSourceBusinessType(voucher.getInnerBusinessType());
                    consignVoucher.setOrigin(voucher.getOrigin());
                    consignVoucher.setOriginBusinessType(voucher.getOriginBusinessType());
                    consignVoucher.setReturned(isReturn);
                    consignVoucher.setBusinessType(isReturn ? BusinessType.CUSTOMER_CONSUME_RETURN_VOUCHER : BusinessType.CUSTOMER_CONSUME_ORDER_VOUCHER);
                    consignVoucher.setInvoiced(true);
                    consignVoucher.setCustomerExpand(voucher.getCustomerExpand());

                    vis.stream().forEach(vi -> vi.setVoucher(consignVoucher));
                    consignVoucher.setVoucherItems(new HashSet<>(vis));
                    SpringManager.getBean(VoucherService.class).save(consignVoucher);
                });
    }


    @Autowired
    private EntityManager em;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private VoucherRepository voucherRepository;

    /**
     * 暂估款调帐
     **/
    public void changCurrentProvisional() {
        String sql = "select a.id\n" +
                "  from acc_voucher a\n" +
                " where a.voucher_type = 'SUPPLIER_CONSUME'\n" +
                "   and a.amt_total <> 0\n" +
                "   and not exists (select 1\n" +
                "          from acc_ledger_entry b\n" +
                "         where b.source_business_type = 'Voucher'\n" +
                "           and b.source = a.id)\n" +
                "   and rownum <= 100";
        while (true) {
            AtomicInteger i = new AtomicInteger();
            transactionTemplate.execute(status -> {
                try {
                    em.createNativeQuery(sql).unwrap(NativeQueryImpl.class)
                            .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
                            .getResultStream()
                            .forEach(m -> {
                                Voucher voucher = voucherRepository.findOne((String) ((Map) m).get("ID"));
                                if (voucher != null) {
                                    changCurrentProvisional(voucher, -1);
                                }
                                i.getAndIncrement();
                            });
                    return null;
                } catch (Exception e) {
                    throw e;
                }
            });
            if (i.get() < 100) {
                break;
            }
        }
    }
}
